import torch
from torch import nn
from torch.nn import functional as F

# 在构造自定义块之前，我们先回顾一下多层感知机（4.3节 ）的代码。
# 下面的代码生成一个网络，其中包含一 个具有256个单元和ReLU激活函数的全连接隐藏层，然后是一个具有10个隐藏单元且不带激活函数的全连接输出层
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.randn(2, 20)
net(X)

# 自定义块，在下面的代码片段中，我们从零开始编写一个块。它包含一个多层感知机，其具有256个隐藏单元的隐藏层和一 个10维输出层。
# 注意，下面的MLP类继承了表示块的类。我们的实现只需要提供我们自己的构造函数（Python中 的__init__函数）和前向传播函数。
class MLP(nn.Module):
    # 用模型参数声明层。这里，我们声明两个全连接的层
    def __init__(self):
        # 调用MLP的父类Module的构造函数来执行必要的初始化。
        # 这样，在类实例化时也可以指定其他函数参数，例如模型参数params
        super().__init__()
        self.hidden = nn.Linear(20, 256)  # 隐藏层
        self.out = nn.Linear(256, 10)  # 输出层
    # 定义模型的前向传播，即如何根据输入X返回所需的模型输出
    def forward(self, X):
        # 注意，这里我们使用ReLU的函数版本，其在nn.functional模块中定义。
        return self.out(F.relu(self.hidden(X)))
# 测试自定义的MLP
net = MLP()
net(X)

# 现在我们可以更仔细地看看Sequential类是如何工作的，只需要定义两个关键函数：
# 1. 一种将块逐个追加到列表中的函数；2. 一种前向传播函数，用于将输入按追加块的顺序传递给块组成的"链条"。
class MySequential(nn.Module):
    def __init__(self, *args):
        super(MySequential, self).__init__()
        for idx, module in enumerate(args):
            # 这里，module是Module子类的一个实例。我们把它保存在'Module'类的成员。变量_modules中。_module的类型是OrderedDict
            self._modules[str(idx)] = module
    def forward(self, X):
        # OrderedDict保证了按照成员添加的顺序遍历它们
        for block in self._modules.values():
            X = block(X)
        return X
# 使用方法与之前的Sequential一样
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)

# 在前向传播函数中实现代码
class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super(FixedHiddenMLP, self).__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变
        self.rand_weight = torch.rand(20, 20, requires_grad=False)  # rand()函数生成均匀分布在(0,1)的随机数，而randn()生成标准正态分布的随机数
        self.linear = nn.Linear(20, 20)
    def forward(self, X):
        X = self.linear(X)
        # 使用创建的常量参数以及relu和mm函数
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # 复用全连接层。这相当于两个全连接层共享参数
        X = self.linear(X)
        # 控制流
        while X.abs().sum() > 1:  # 在L1范数大于1的条件下
            X /= 2
        return X.sum()
net = FixedHiddenMLP()
net(X)

# 我们可以混合搭配各种组合块的方法。在下面的例子中，我们以一些想到的方法嵌套块
class NestMLP(nn.Module):
    def __init__(self):
        super(NestMLP, self).__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
                                 nn.Linear(64, 32), nn.ReLU()
                                 )
        self.linear = nn.Linear(32, 16)
    def forward(self, X):
        return self.linear(self.net(X))
chimera = nn.Sequential(NestMLP(), nn.Linear(16 ,20), FixedHiddenMLP())
chimera(X)

class test2(nn.Module):
    def __init__(self):
        super(test2, self).__init__()
        self.net1 = nn.Sequential(nn.Linear(20, 256), nn.ReLU(),
                                  nn.Linear(256, 10))
        self.net2 = nn.Sequential(nn.Linear(10, 512), nn.ReLU(),
                                  nn.Linear(512, 10))
        self.linear = nn.Linear(10, 1)
    def forward(self, X):
        X = self.net1(X)
        X = self.net2(X)
        return self.linear(F.relu(X))
net = test2()
net(X)